< go back

how i made my oc directory

August 12, 2026 - site

This will be less of a tutorial, more of a documentation of how I made my oc directory! Both because I'm really happy with how it turned out, and to keep a little "guide" here as future reference in case I forget how it works.

what I wanted

When starting this project, I decided that there were a few things I wanted:

  1. The ability to add new pages easily in markdown and have it automatically formatted
  2. Something like Toyhouse's tabs where each character profile is structured like a bunch of mini-tabs with info in each of them (eg. I want to be able to make a new "headcanons" tab for an oc in markdown and have it automatically show up on that oc's profile)
  3. Custom CSS for each character so I can theme them to the oc! Buuut this should be optional— I should be able to make profiles with no custom styling and still have it displayed properly
  4. Automatically updating art galleries under each character
  5. Ability to use wikilinks in obsidian and have it function properly as links when translated to html

inspirations

making the base layout

I started with making a basic layout for the directory before playing around with any templating code and whatnot. Just pure html and css for now.

screenshot of my oc directory page with a header ft art of my ocs, navigation bar, and a brief welcome message

Honestly, not much to say here. It's literally just me styling a page. The basic layout is a grid with a header, nav-bar, and content.

Both the nav-bar and content area are overflow: auto (scrollable). This, for some reason, took so long to figure out (idk why but scrollable areas inside grids mess me up so much), specifically the content box since I didn't want the whole content area to be scrollable, only the bottom part (excluding the title). Anyways, I fixed it by giving the grid a defined height (100vh) and setting the content area to be overflow: hidden, then the scrollable area to be overflow: auto.

I made sure that any colours I used are all variables instead of hardcoded into each style (tbh I should be doing this for all pages, but sometimes I'm too lazy to set up variables lmao). This means I can make simple custom themes for each character by changing the colours for each variable.

markdown pages

All my oc pages are written in md, where I can add small html snippets if I really wanted to. Every oc has their own folder with all there pages inside. Each folder has a json file (eg. ather/ has ather.json inside) with properties I want all the files in the folder to have (saves me the trouble of inputting them manually each time). Here's an example of what ather.json looks like:

{
    "css":"ather.css",
    "stamps":"ather-stamps.njk",
    "oc": ["ather"]
}

I will go over each of these later, but tldr: css and stamps is for styling/theming purposes, oc is for tagging "ather" so that whenever I open one of her pages, every other page also tagged "ather" will show up in the navigation bar too!

navigation bar

Once again, I would like to put a warning for bad spaghetti code up ahead from someone who has no idea what they're doing LMAO

Right, so here's what I wanted from the navigation bar:

  1. Automatically updating with links to related pages. For example, let's say oc1 has a personality page and a design page. Then I add a page for oc1 and oc2's relationship. If I open one of oc1's pages, I want the nav-bar to automatically link the relationship page as a possible related page. If that makes sense... TLDR: I want to make lots of pages for an oc (like different tabs) and have the navigation bar auto-update to show all these pages for that oc's profile.
  2. Custom, hard-coded navigation bar if I want. For some pages, I might want more control over the nav-bar instead of having it be fully automatic.

I decided that I would have a hard-coded navigation bar for the main homepage. I put it into oc-main-nav.html.

I created an oc property on all my oc related pages where I would tag the names of ocs the page is related to. This is how I would check for related pages— When I click on a page, every other page that also has the same oc tag shows up.

In my eleventy.config.js file, I created a custom collection for all oc tags after LOTS of trial and error ;-; Basically every line is commented to try and get my beginner js brain to understand what on earth I was doing, so feel free to read through that if you're interested:

eleventyConfig.addCollection("oc", collection => { //creates a new collection called "oc"
    const pages = collection.getAll() //gets all pages
  
    const octag = pages.reduce((octag, page) => { //octag is accumulation of everything so far. post is current value

      if(page.data.oc){ //only check for pages that have the oc property (bc idk how to filter it in the og step)
        const tags = Array.isArray(page.data.oc)  //for every post, look for the oc property. 
          ? page.data.oc //if it's an array (multiple oc tags), set tags to it. 
          : [page.data.oc] //else, turn it into an array. (if there's only one oc, it will not be an array)
        
        tags.forEach(tag => { //loop through every oc tag in this page (every entry in the tags array)
        
          if (!octag[tag]) { //octag is an object. this checks to see if tag exists as a key inside octag yet. 
            octag[tag] = []; //if not, it creates an empty array (tag: [])
          }

          octag[tag].push(page); //adds current page to the array under the tag (tag: [page])
        });
      }
      return octag;
    }, {})
  
    //sort pages alphabetically
    for (const tag in octag) {
      octag[tag].sort((a, b) => {
        const titleA = (a.data.title || a.fileSlug).toLowerCase(); //use fileslug if title doesn't exist
        const titleB = (b.data.title || b.fileSlug).toLowerCase(); //all lowercase bc otherwise uppercase will be sorted first
        
        return titleA < titleB ? -1 : 1
      });
    }

    return Object.entries(octag)
  })

Now for generating the navigation back in my oc layout template! Here's the code I added into oc-main.njk:

<div class="nav">
  <div class="dynamic-nav">
      {%set customNav = false%}
      {%for currentPageTag in oc%}
          {%for tag, tagPages in collections.oc%}
              {%if tag == currentPageTag%}
                  {% set navPath = "src/_includes/oc/" + tag + "-nav.njk" %}
                  {% if navPath | fileExists %}
                      {% include navPath %}

                  {%else%}
                      <h2>{{tag}}</h2>
                      <div>
                          {%for page in tagPages%}
                              {% if page.data.title%}
                                  <a href="{{page.url}}"><span>{{page.data.title.slice(2)}}</span></a>
                              {%else%}
                                  <a href="{{page.url}}"><span>{{page.fileSlug | replace("-", " ")}}</span></a>
                              {%endif%}
                          {%endfor%}

                      </div>
                  {%endif%}
                  {%set customNav = true%}
              {%endif%}
          {%endfor%}
      {%endfor%}
      
  </div>
  {%if customNav%}
      <div class="main-nav">
          <a href="/oc/index.html"><span>< directory home</span></a>
      </div>
  {%else%}
  <div class="main-nav">{% include "_partials/oc-main-nav.njk"%}</div>
  {%endif%}
</div>

My navigation bar is split into 2 sections: a dynamic-nav div and a main-nav div. Dynamic-nav is for character specific pages, and main-nav is for the homepage navigation (links to all different stories/projects).

The reason I have them separated is that initially, I had the main-nav show up in all pages under the custom links. I later realised that this was kind of confusing to navigate so instead of showing the whole navigation bar, it just shows a button to go back to home. Kind of makes the main-nav div redundant, but whatever.

Sooo what's happening in this code???

First, I make a new variable called customNav to false. Then I loop through every tag the page has in the oc property. For each tag, I find the corresponding tag in the oc collection.

If I want a custom, hard-coded navigation bar for an oc, I will make a file called "oc-nav.njk" inside "/src/_includes/oc/". Therefore, if it detects that this file exists, it will add the contents of that file and call it a day.

Otherwise, it will automatically generate my navigation bar. I'm basically looping through every page under the current tag and adding a link for it.

If customNav is true, I add a link to go back home underneath. If customNav is false, the first part (the dynamic-nav div) will be empty, and the second (main-nav div) will be filled with the default homepage navigation (oc-main-nav.njk).

custom themes

The one thing I NEED for any character profile is the ability to style it based on the oc >:D But I also didn't want to NEED to style everything from scratch for every oc in case I just wanted to quickly jot down some lore. A good comprimise was to make a default style/template, and add the ability to have custom colours and images for each oc if so desired.

I have this in my base oc-main.njk layout:

<style>
    {% include "oc/oc-main.css"%}

    {%if css%}
      {% if "src/_includes/oc/" + css | fileExists %}
          {% include "oc/" + css %}
      {%endif%}
    {%endif%}
</style>

oc-main.css is for all the default styling that is needed.

If I want to use a custom theme, I will specify that by putting "css: 'file.css'" in the properties of the page. Then, the layout will detect that, and if that file exists, it will add that code inside the styles part.

Yes, it would probably be better practice to keep all css in a separate .css file rather than putting it inside the style tag, but idk I find it easier to code with everything in one file so this is how my base.njk is set up.

Here are the main things I customise with my custom css files:

Colours! All my colours are variables, so it's super easy to just change the colours of the variables and have it update all throughout the page

Backgrounds! I just target the classes with backgrounds and add a new background-image url. In the future, I think I'll change it so that the urls are all inside a variable too to make it easier to change.

Header! The default header I have is one image, but I wanted to be able to put 3 images for some character profiles (mainly because the toyhouse template I was using had this and I wanted to keep the look). I made the header into a grid and set the background image to the default one. Then, I added 3 divs inside and set them to display: none so they don't show up by default. For profiles where I want 3 images, I set their display to block and change their background images!

Stamps!! I wanted stamps in the bottom of my character profiles IF I wanted them (so no stamps by default). For characters with stamps, I have a "name-stamps.html" file that is literally just a list of images (the stamps). I specify this in a "stamps: 'file-name'" property. In my layout file, I added a footer that checks to see if the stamps property exists, then plops that into the footer. Otherwise, the footer is empty and will thus not be displayed.

And tada!! Custom styling if so desired:

gif freezing

I've discovered that having my stamps and animated pfps adds a lot of movement. I followed this tutorial to get a gif toggle working. It didn't work at first (the sizing got all weird) but I managed to fix it buuut I don't remember how (sorry). I think it had something to do with adding width: 100% to some of my images.

art galleries

Making the galleries was basically just copying code from my existing art gallery with some extra filters and restyling it.

All my art pages are tagged with "creation" and any additional tags (eg. character name, type of art, etc). I filter through getting pages in collection.oc to get all pages tagged with that character, then filter to only use pages that also have the "creation" tag (so only art pages).

I decided that I wanted separate galleries for doodles, physical items, etc. Since these are tagged in my art page, I just filtered using those tags for each gallery.

Here's an example of what Ather's main gallery looks like:

<div class="gallery">
    {% for post in collections.ather | reverse %} <!--get all pages with ather tag-->
        {% if "creation" in post.data.tags and "doodle" not in post.data.tags and "physical-item" not in post.data.tags and "sketchbook" not in post.data.tags %} <!--filters
            <a href="{{ post.url }}"> <!--this section is just styling each "card" using properties from the page-->
                <div class="block">
                        <div class="top-bar">
                            <span>{{ post.data.title }}.{{ post.data.ext }}</span>
                            <span>x</span>
                        </div>

                    <!--gets the path of the image. they're stored in folders by year, which is retrieved through slicing the file path. title and extension are manually inputted properties for each page--> 
                    <img src="/art/img/{{ (post.filePathStem or "untitled").slice(5,9) }}/{{ post.data.title }}.{{ post.data.ext }}" loading="lazy">
                    <div class="info">
                            <span>{{ post.date | postDate }}</span> <!--postDate is a custom filter I have to make the looong date 11ty default to into a short, readable one-->
    
                            {% set tags = post.data.tags | exclude("creation") | exclude("ather") %} <!--displays tags that aren't "creation" or "ather", since those are a given-->
                                {% for tag in tags %}
                                    <span class="tags">{{ tag }}</span>
                                {% endfor %}
                    </div>
                </div>
            </a>
            {%endif%}
        {% endfor %}
</div>

And here's what it looks like, separated by type of art !!

wikilinks

After much deliberation, I finally decided to migrate my worldbuilding notes over to my site. I initially had it mixed up with my personal obsidian vault (since I like keeping everything in one place) but decided that manually copy pasting stuff would be wayyy too tedious.

Since my worldbuilding notes are very much interlinked with each other, obsidian's wikilinks was something I 100% needed to be able to use. I ended up finding this plugin to get it working!

Installation was basically copy-pasting from the instructions on github. Side note: remember that this is a plugin for markdown-it, meaning you need to install markdown-it too. For some reason, I missed this part even though it's literally in the name LMAO.

Anyways, one problem I ran into was that markdown-it displays html code instead of parsing it. I have some html snippets in some pages (eg. I used a grid for likes and dislikes on some ocs) so I needed this to work. I set the html option to true in my eleventy config file (const md = new MarkdownIt({html:true})) which fixed it.

private pages

Sometimes, there are pages that I want to keep private because they're wips or just because I don't want to share these notes. To keep entire pages private, I've found that I can just add permalink: false and eleventyExcludeFromCollections: true to the properties and the page basically won't exist.

Sometimes, there are sections within pages that I want to keep private. In these cases, I just wrap the section in {% comment %} and {% endcomment %}. Then it won't show up in the published site, not even in view source!

closing thoughts

Aaaad, I think that's it!! You can check it out here!! I'm incredibly happy with how this directory turned out and I think it's the part of my site I'm most proud of :D It's also the part that took the longest ahaha— that navigation bar system took a while to figure out lol.

I've wanted to make my own oc wiki / database for sooo long, and I've made extensive html templates in the past but just never used them. I think learning 11ty and being able to write in markdown (in obsidian), as well as the ability to easily add new pages mimicking toyhouse's tabs has made it SO much easier to write new lore and make me actually enjoy using it!

(seriously, if you're comfortable with basic html/css, try learning to use a static site generator like 11ty !! the ability to use markdown makes it so much nicer to update your site and write articles / posts / whatnot)

Now I can finally show people the oc lore that's floating about in my head, and do so with a PRETTY eye-candy layout :>